package nullInvader.Engine;

import java.awt.Point;

import nullInvader.Control.GameControl;

/**
 * Applikationsutveckling i Java, 5DV135 HT12 Project
 * 
 * File: Player.java Description: A Class that represents the player for the
 * current game controls the units and the towers is also used to keep track of
 * the amount of funds available
 * 
 * Date: 17 december 2012
 * 
 * cs-user: dv11flh
 * 
 * @author Fredrik Loch
 */
public class Player implements Runnable {

    //
    // Fields
    //

    protected volatile boolean killed;
    protected GameControl gc;
    protected int cash;
    protected Map currentMap;
    protected TowerManager tm;
    protected UnitManager um;
    protected int boughtUnits;
    protected int sleepTime;
    protected int cheapestUnit;

    //
    // Constructors
    //

    public Player(Map currentMap, int sleepTime, GameControl gc) {
	this.currentMap = currentMap;
	cash = currentMap.getStartCash();
	this.sleepTime = sleepTime;
	try {
	    for (UnitInfo p : currentMap.getUnitPrices()) {
		if (cheapestUnit == 0) {
		    cheapestUnit = p.getPrice();
		} else {
		    cheapestUnit = (cheapestUnit > p.getPrice()) ? p.getPrice()
			    : cheapestUnit;
		}
	    }
	} catch (Exception e) {
	    cheapestUnit = 300;
	}

	killed = false;
	this.gc = gc;

	tm = new TowerManager(this, currentMap.getTowerList());
	um = new UnitManager(currentMap.getGoal(), this);
    };

    //
    // Methods
    //

    /**
     * Run method that runs the game mechanics for the units and the towers
     * 
     * @see TowerManager
     * @see UnitManager
     */
    @Override
    public void run() {
	try {
	    while (!killed) {
		if (!gc.isPaused()) {
		    um.tick();
		    tm.tick();
		    Thread.sleep(sleepTime);
		} else {
		    Thread.sleep(sleepTime / 24);
		}
	    }
	} catch (InterruptedException iex) {
	}
    }

    /**
     * Method to add unit to the game takes the unitType as a string and checks
     * for a unit price and decides if the purchase is allowed
     * 
     * @param unitType
     * @return true if successful
     * @see Unit
     */
    public Boolean buyUnit(String unitType) {
	Unit u = null;
	int price = 0;
	if (unitType.equals("Knight")) {
	    try {
		price = ((currentMap.getUnitPrices().get(0) == null) ? 300
			: currentMap.getUnitPrices().get(0).getPrice());
	    } catch (IndexOutOfBoundsException e) {
		price = 300;
	    }
	    if (cash >= price) {
		u = new UnitKnight(currentMap.getStart(), this, price);
		um.addUnit(u);
		boughtUnits++;
		modifyCash(-u.getPrice());
		return true;
	    } else {
		return false;
	    }
	} else if (unitType.equals("Teleporter")) {
	    try {
		price = ((currentMap.getUnitPrices().get(1) == null) ? 500
			: currentMap.getUnitPrices().get(1).getPrice());
	    } catch (IndexOutOfBoundsException e) {
		price = 500;
	    }
	    if (cash >= price) {
		u = new UnitTeleporter(currentMap.getStart(), this, price);
		um.addUnit(u);
		boughtUnits++;
		modifyCash(-u.getPrice());
		return true;
	    } else {
		return false;
	    }
	}
	return false;
    }

    /**
     * Used to modify the amount of cash the player
     * 
     * @param i
     */
    public void modifyCash(int i) {
	cash += i;
    }

    /**
     * Method to set the threads status to kill
     */
    public boolean kill() {
	killed = true;
	return killed;
    }

    /**
     * Checks the current status of the game and reports to gameControl in the
     * event of Win or Loss
     * 
     * @return int
     * @param unitsInGoal
     * @param aliveUnits
     * @see GameControl
     */
    public int checkGameStatus(int unitsInGoal, int aliveUnits) {

	if (unitsInGoal >= currentMap.getGoalScore()) {
	    try {
		gc.reportWin(getScore());
	    } catch (Exception e) {
		System.err.println(e.toString());
	    }
	    return 1;
	} else if (unitsInGoal < currentMap.getGoalScore() && aliveUnits == 0) {
	    if (cash < cheapestUnit) {
		try {
		    gc.reportLoss();
		} catch (Exception e) {
		    System.err.println(e.toString());
		}
		return 2;

	    }
	}
	return 0;
    }

    /**
     * Returns the current amount of cash the player holds
     * 
     * @return int
     */
    public int getCash() {
	return cash;
    }

    /**
     * Returns the zone to the north of the parameter
     * 
     * @param p
     * @return Zone
     */
    public Zone getPosToNorth(Point p) {
	Point test = new Point(p.x, p.y + 1);
	return currentMap.getZone(test);
    }

    /**
     * Returns the zone to the south of the parameter
     * 
     * @param p
     * @return Zone
     */
    public Zone getPosToSouth(Point p) {
	Point test = new Point(p.x, p.y - 1);
	return currentMap.getZone(test);
    }

    /**
     * Returns the zone to the east of the parameter
     * 
     * @param p
     * @return Zone
     */
    public Zone getPosToEast(Point p) {
	Point test = new Point(p.x + 1, p.y);
	return currentMap.getZone(test);
    }

    /**
     * Returns the zone to the west of the parameter
     * 
     * @param p
     * @return Zone
     */
    public Zone getPosToWest(Point p) {
	Point test = new Point(p.x - 1, p.y);
	return currentMap.getZone(test);
    }

    /**
     * Returns the zone of the parameter
     * 
     * @param p
     * @return Zone
     */
    public Zone getCurrentZone(Point p) {
	return currentMap.getZone(p);
    }

    /**
     * Returns the current score for this player.
     * 
     * @return int
     */
    public int getScore() {
	// boughtUnits must be checked for 0 to prevent
	// "divide by zero"
	// exception. Cast to float is to guarantee float division.

	int score = (int) ((boughtUnits > 0) ? (((float) currentMap
		.getGoalScore() / boughtUnits) * 1000) : (currentMap
		.getGoalScore() * 1000));
	return score;
    }
}
